﻿#pragma once

#include <utility>
#include <type_traits>

/// @cond ignored
namespace TypeAlias{

	template<class TypeTag>
	struct Tagged{};

	template< class C, class T, template< class > class ...Ops>
	class Alias;

	namespace details
	{
		template< typename T >
		struct remove_cvref
		{
			typedef typename std::remove_cv< typename std::remove_reference<T>::type >::type type;
		};

		struct StrongAliasTag{};

		template<class T>
		struct IsTypeAliasHelper
		{
			static std::false_type test(...);

			template< class C, class U, template< class > class ...Ops>
			static std::true_type test(Alias<C, U, Ops...> *);

			static constexpr bool value = decltype(test((T*)0))::value;
		};

		template<class C, class = void>
		struct IsTagged
		{
			static constexpr bool value = false;
		};

		template<class C>
		struct IsTagged< Tagged<C> >
		{
			static constexpr bool value = true;
		};

		template<class C, class = void>
		struct IsDerived
		{
			static constexpr bool value = !IsTagged<C>::value;
		};
	}

	template<class T>
	struct IsAlias
	{
		static constexpr bool value = details::IsTypeAliasHelper<T>::value && std::is_base_of<details::StrongAliasTag, T>::value;
	};

	template<class T>
	constexpr inline bool IsAliasV = IsAlias<T>::value;


	template< class C, class T, template< class > class ...Ops>
	class Alias :public Ops< Alias< C, T, Ops...> > ..., private details::StrongAliasTag
	{
		T _v{};
	public:
		using underlying_type = T;

		Alias() = default;

		template<class U = T, class = std::enable_if_t< std::is_constructible_v<T, U&&> && !IsAliasV<typename details::remove_cvref<U>::type> > >
		Alias(U&& value) :_v(std::forward<U>(value)){}

		T&			get() & noexcept{ return _v; }
		T const&	get() const & noexcept{ return _v; }

		T&&			get() && noexcept{ return std::move(_v); }
		T const&&	get() const && noexcept{ return std::move(_v); }
	};

	template<class>
	class ExplictConvertible;

	template<class C, class T, template< class ST > class ...Ops>
	class ExplictConvertible< Alias< C, T, Ops...> >
	{
		using base_type = Alias< C, T, Ops...>;
		using self_type = std::conditional_t<details::IsDerived<C>::value, C, base_type>;
	public:
		explicit operator T const&() const & noexcept { return static_cast<const base_type &>(*this).get(); }
		explicit operator T &() & noexcept{ return static_cast<base_type&>(*this).get(); }

		explicit operator T const&&() const && noexcept{ return static_cast<const base_type&&>(*this).get(); }
		explicit operator T && () && noexcept{ return static_cast<base_type &&>(*this).get(); }
	};


	template<class>
	class ImplictConvertible;

	template<class C, class T, template< class ST > class ...Ops>
	class ImplictConvertible<Alias<C, T, Ops...> >
	{
		using base_type = Alias< C, T, Ops...>;
		using self_type = std::conditional_t<details::IsDerived<C>::value, C, base_type>;
	public:
		operator T const&() const & noexcept{ return static_cast<const base_type &>(*this).get(); }
		operator T &() & noexcept{ return static_cast<base_type&>(*this).get(); }

		operator T const&&() const && noexcept{ return static_cast<const base_type&&>(*this).get(); }
		operator T && () && noexcept{ return static_cast<base_type &&>(*this).get(); }
	};


	template<class>
	class EqualityComparable;

	template< class C, class T, template< class ST > class ...Ops>
	class EqualityComparable<Alias< C, T, Ops...> >
	{
		using base_type = Alias< C, T, Ops...>;
		using self_type = std::conditional_t<details::IsDerived<C>::value, C, base_type>;

		friend auto operator==(self_type const& lhs, self_type const& rhs)->decltype(std::declval<const T&>() == std::declval<const T&>())
		{
			return static_cast<const base_type&>(lhs).get() == static_cast<const base_type&>(rhs).get();
		}

		friend auto operator!=(self_type const& lhs, self_type const& rhs)->decltype(std::declval<const T&>() != std::declval<const T&>())
		{
			return static_cast<const base_type&>(lhs).get() != static_cast<const base_type&>(rhs).get();
		}
	};

	template<class>
	class OrderingComparable;

	template<class C, class T, template< class ST > class ...Ops>
	class OrderingComparable<Alias<C, T, Ops...> >
	{
		using base_type = Alias< C, T, Ops...>;
		using self_type = std::conditional_t<details::IsDerived<C>::value, C, base_type>;

		friend auto operator<(self_type const& lhs, self_type const& rhs)->decltype(std::declval<const T&>() < std::declval<const T&>())
		{
			return static_cast<const base_type&>(lhs).get() < static_cast<const base_type&>(rhs).get();
		}

		friend auto operator<=(self_type const& lhs, self_type const& rhs)->decltype(std::declval<const T&>() <= std::declval<const T&>())
		{
			return static_cast<const base_type&>(lhs).get() <= static_cast<const base_type&>(rhs).get();
		}

		friend auto operator>(self_type const& lhs, self_type const& rhs)->decltype(std::declval<const T&>() > std::declval<const T&>())
		{
			return static_cast<const base_type&>(lhs).get() > static_cast<const base_type&>(rhs).get();
		}

		friend auto operator>=(self_type const& lhs, self_type const& rhs)->decltype(std::declval<const T&>() >= std::declval<const T&>())
		{
			return static_cast<const base_type&>(lhs).get() >= static_cast<const base_type&>(rhs).get();
		}
	};
}

namespace std{
	template< class C, class T, template< class > class ...Ops>
	struct hash<TypeAlias::Alias< C, T, Ops...> >{
		auto operator()(TypeAlias::Alias<C, T, Ops...> const& v) const noexcept{
			return hash<T>{}(v.get());
		}
	};
}

/// @endcond
